/*** Created Time: 2022年5月16日 21:02:46 ***/
/*** Author: Eric                        ***/

// 中断相关函数

#include <onix/interrupt.h>
#include <onix/global.h>
#include <onix/debug.h>
#include <onix/printk.h>
#include <onix/stdlib.h>
#include <onix/io.h>
#include <onix/assert.h>

#define     LOGK(fmt, args...)  DEBUGK(fmt, ##args)
// #define     LOGK(fmt, args...)

#define     ENTRY_SIZE          0x30    // 48个中断处理入口函数

// 8259A的端口定义
#define     PIC_M_CTRL          0x20    // 主片的控制端口
#define     PIC_M_DATA          0x21    // 主片的数据端口
#define     PIC_S_CTRL          0xa0    // 从片的控制端口
#define     PIC_S_DATA          0xa1    // 从片的数据端口
#define     PIC_EOI             0x20    // 通知中断控制器中断结束

gate_t idt[IDT_SIZE];   // 中断描述符表
pointer_t idt_ptr;      // 中断描述符表指针，定义了IDT的起始基地址和大小

handler_t handler_table[IDT_SIZE];                  // 中断处理函数表
extern handler_t handler_entry_table[ENTRY_SIZE];   // 记录了每个中断入口函数的指针,在handler.asm中定义
extern void syscall_handler(void);                  // 系统调用函数句柄，在handler.asm中定义
extern void page_fault(void);

// 辅助函数声明
void default_handler(int vector);
void exception_handler(
    int vector,
    u32 edi, u32 esi, u32 ebp, u32 esp,
    u32 ebx, u32 edx, u32 ecx, u32 eax,
    u32 gs, u32 fs, u32 es, u32 ds,
    u32 vector0, u32 error, u32 eip, u32 cs, u32 eflags);
void pic_init(void);
void idt_init(void);

static char *messages[] = {
    "#DE Divide Error\0",
    "#DB RESERVED\0",
    "--  NMI Interrupt\0",
    "#BP Breakpoint\0",
    "#OF Overflow\0",
    "#BR BOUND Range Exceeded\0",
    "#UD Invalid Opcode (Undefined Opcode)\0",
    "#NM Device Not Available (No Math Coprocessor)\0",
    "#DF Double Fault\0",
    "    Coprocessor Segment Overrun (reserved)\0",
    "#TS Invalid TSS\0",
    "#NP Segment Not Present\0",
    "#SS Stack-Segment Fault\0",
    "#GP General Protection\0",
    "#PF Page Fault\0",
    "--  (Intel reserved. Do not use.)\0",
    "#MF x87 FPU Floating-Point Error (Math Fault)\0",
    "#AC Alignment Check\0",
    "#MC Machine Check\0",
    "#XF SIMD Floating-Point Exception\0",
    "#VE Virtualization Exception\0",
    "#CP Control Protection Exception\0",
};


// 通知中断控制器，中断处理结束
void send_eoi(int vector)
{
    if (vector >= 0x20 && vector < 0x28) {
        outb(PIC_M_CTRL, PIC_EOI);  // 相当于写主片的OCW2
    }
    if (vector >= 0x28 && vector < 0x30){
        outb(PIC_M_CTRL, PIC_EOI);  // 相当于写主片的OCW2
        outb(PIC_S_CTRL, PIC_EOI);  // 相当于写从片的OCW2
    }
}

// 设置中断处理函数
void set_interrupt_handler(u32 irq, handler_t handler)
{
    assert(irq >= 0 && irq < 16);
    handler_table[IRQ_MASTER_NR + irq] = handler;   // 重新设置中断处理函数指针
}

// 设置中断屏蔽字函数
void set_interrupt_mask(u32 irq, bool enable)
{
    assert(irq >= 0 && irq < 16);
    u16 port;
    
    if (irq < 8) {
        port = PIC_M_DATA;
    } else {
        port = PIC_S_DATA;
        irq -= 8;
    }

    if (enable) {
        outb(port, inb(port) & ~(1 << irq));
    } else {
        outb(port, inb(port) | (1 << irq));
    }
}

// 清除IF位，返回设置之前的值
bool interrupt_disable(void)
{
    asm volatile(
        "pushfl\n"        // 将当前 eflags 压入栈中
        "cli\n"           // 清除 IF 位，此时外中断已被屏蔽
        "popl %eax\n"     // 将刚才压入的 eflags 弹出到 eax
        "shrl $9, %eax\n" // 将 eax 右移 9 位，得到 IF 位
        "andl $1, %eax\n" // 只需要 IF 位
    );
}

// 获得IF位
bool get_interrupt_state(void)
{
    asm volatile(
        "pushfl\n"        // 将当前 eflags 压入栈中
        "popl %eax\n"     // 将压入的 eflags 弹出到 eax
        "shrl $9, %eax\n" // 将 eax 右移 9 位，得到 IF 位
        "andl $1, %eax\n" // 只需要 IF 位
    );
}

// 设置IF位
void set_interrupt_state(bool state)
{
    if (state) {
        asm volatile("sti\n");
    } else {
        asm volatile("cli\n");
    }
}

// 外中断默认处理函数
void default_handler(int vector)
{
    send_eoi(vector);   // 通知中断控制器，中断处理结束
    DEBUGK("[%x] default interrupt called...\n", vector);
}


// 异常(内中断)默认处理函数
void exception_handler(
    int vector,
    u32 edi, u32 esi, u32 ebp, u32 esp,
    u32 ebx, u32 edx, u32 ecx, u32 eax,
    u32 gs, u32 fs, u32 es, u32 ds,
    u32 vector0, u32 error, u32 eip, u32 cs, u32 eflags)
{
    char *message = NULL;
    if(vector < 22) {
        message = messages[vector];
    } else {
        message = messages[15];     // "--  (Intel reserved. Do not use.)\0"
    }

    printk("\nEXCEPTION : %s \n", messages[vector]);
    printk("   VECTOR : 0x%02X\n", vector);
    printk("    ERROR : 0x%08X\n", error);
    printk("   EFLAGS : 0x%08X\n", eflags);
    printk("       CS : 0x%02X\n", cs);
    printk("      EIP : 0x%08X\n", eip);
    printk("      ESP : 0x%08X\n", esp);
    // 阻塞
    hang();
}

// 初始化中断控制器，80486中的8259A芯片
// 中断控制器编程，在8259A内部有两组寄存器：
//   初始化命令寄存器组，保存初始化命令字 (Initialization Command Words, ICW), ICW 共 4 个，ICW1 ~ ICW4；
//   操作命令寄存器组，保存操作命令字 (Operation Command Word, OCW), OCW 共 3 个，OCW1 ~ OCW3;
// ICW做初始化，用来确定是否需要级联，设置起始中断向量号，设置中断结束模式，要求严格的顺序，必须依次写入 ICW1、ICW2、ICW3、ICW4
// OCW来操作控制8259A。中断屏蔽和中断结束，就是通过往8259A端口发送 OCW 实现的。OCW 的发送顺序不固定，3 个之中先发送哪个都可以
// 注：
//   8259A就2个端口地址，如何识别4个ICW和3个 OCW :
//   ICW1，OCW2，OCW3 是用偶地址端口主片的 0x20  或从片的 0xA0 写入。
//   ICW2~ICW4 和 OCW1 是用奇地址端口主片的 0x21 或从片的 0xA1 写入。
//   以上 4 个 ICW 要保证一定的次序写入，8259A 就知道写入端口的数据是什么
//   而 OCW 的写入与顺序无关，并且 ICW1 和 OCW2, OCW3 的写入端口是一致的
//   因此通过各控制字中的第 4~3 标识位，通过这两位的组合来唯一确定某个控制字
void pic_init(void)
{
    outb(PIC_M_CTRL, 0b00010001); // ICW1: 边沿触发, 级联 8259, 需要ICW4.
    outb(PIC_M_DATA, 0x20);       // ICW2: 起始中断向量号 0x20
    outb(PIC_M_DATA, 0b00000100); // ICW3: IR2接从片，对于主片，ICW3 中置1的那一位对应的IRQ接口用于连接从片，若为0则表示接外部设备
    outb(PIC_M_DATA, 0b00000001); // ICW4: 设置8259A的工作模式，8086模式, AEOI=0，表示非自动，即手动结束中断
                                  //       可以在中断处理程序中手动向 259A的主、从片发送 EOI 信号

    outb(PIC_S_CTRL, 0b00010001); // ICW1: 边沿触发, 级联 8259, 需要ICW4.
    outb(PIC_S_DATA, 0x28);       // ICW2: 起始中断向量号 0x28
    outb(PIC_S_DATA, 2);          // ICW3: 设置从片连接到主片的 IR2 引脚,设置从片连接主片只需要在从片上指定主片用于连接自己的IRQ接口即可
    outb(PIC_S_DATA, 0b00000001); // ICW4: 8086模式, 正常EOI

    outb(PIC_M_DATA, 0b11111111); // 写主片OCW1屏蔽字寄存器，用来屏蔽连接在8259A上的外部设备的中断信号，关闭所有中断
    outb(PIC_S_DATA, 0b11111111); // 写从片OCW1屏蔽字寄存器，主从片的OCW1受EFLAGS的IF位约束，关闭所有中断
}


// 初始化中断描述符和中断处理函数数组
void idt_init(void)
{
    for(size_t i=0; i<ENTRY_SIZE; i++) {
        gate_t *gate = &idt[i];     // 将IDT中的第i个中断描述符的地址赋给gate
        handler_t handler = handler_entry_table[i];       // 求中断入口函数的指针

        gate->offset0 = (u32)handler & 0xffff;            // 段位偏移的低位，0~15位
        gate->offset1 = ((u32)handler >> 16) & 0xffff;    // 段位偏移的高位，16~31位
        gate->selector = 1 << 3;    // 代码段
        gate->reserved = 0;         // 保留不用
        gate->type = 0b1110;        // 中断门
        gate->segment = 0;          // 系统段
        gate->DPL = 0;              // 最高权限，内核态
        gate->present = 1;          // 有效
    }

    for(size_t i=0; i<0x20; i++) {
        handler_table[i] = exception_handler;   // 将中断处理函数表的前32个初始化成异常的处理函数
    }

    handler_table[0xe] = page_fault;            // 缺页异常处理函数放到 0xe 的中断处理函数表的位置

    for(size_t i=0x20; i<ENTRY_SIZE; i++) {
        handler_table[i] = default_handler;     // 将中断处理函数表的后16个初始化成外中断的处理函数
    }

    // 初始化系统调用
    gate_t *gate = &idt[0x80];  // 将0x80号的中断描述符设置为syscall_handler
    gate->offset0 = (u32)syscall_handler & 0xffff;            // 段位偏移的低位，0~15位
    gate->offset1 = ((u32)syscall_handler >> 16) & 0xffff;    // 段位偏移的高位，16~31位
    gate->selector = 1 << 3;    // 代码段
    gate->reserved = 0;         // 保留不用
    gate->type = 0b1110;        // 中断门
    gate->segment = 0;          // 系统段
    gate->DPL = 3;              // 用户态，说明用户态可调用syscall_handler函数
    gate->present = 1;          // 有效
    

    idt_ptr.base = (u32)idt;        // IDT的起始基地址
    idt_ptr.limit = sizeof(idt) - 1;
    
    __asm__ __volatile__("lidt idt_ptr\n"); // 加载IDT
}


// 系统中断初始化函数
void interrupt_init(void)
{
    pic_init();     // 初始化中断控制器
    idt_init();     // 初始化中断描述符
}